package com.blogspot.sunserenity.blockcipher.DES;

import java.util.ArrayList;

import com.blogspot.sunserenity.binarytools.BinaryTools;
import com.blogspot.sunserenity.blockcipher.BlockCipher;

public class DESAlgorithm implements BlockCipher {
	
	private final static int[] ip = {
		58,	50,	42,	34,	26,	18,	10,	2,	60,	52,	44,	36,	28,	20,	12,	4,
		62,	54,	46,	38,	30,	22,	14,	6,	64,	56,	48,	40,	32,	24,	16,	8,
		57,	49,	41,	33,	25,	17,	9,	1,	59,	51,	43,	35,	27,	19,	11,	3,
		61,	53,	45,	37,	29,	21,	13,	5,	63,	55,	47,	39,	31,	23,	15,	7
		};
	
	private final static int[] inverseIP = {
		40,	8,	48,	16,	56,	24,	64,	32,	39,	7,	47,	15,	55,	23,	63,	31,
		38,	6,	46,	14,	54,	22,	62,	30,	37,	5,	45,	13,	53,	21,	61,	29,
		36,	4,	44,	12,	52,	20,	60,	28,	35,	3,	43,	11,	51,	19,	59,	27,
		34,	2,	42,	10,	50,	18,	58,	26,	33,	1,	41,	9,	49,	17,	57,	25
		};
	
	private final static int[] E = {
		32,	1,	2, 	3, 	4, 	5,
		4, 	5,	6,	7, 	8, 	9,
		8, 	9,	10,	11, 12,	13,
		12,	13,	14,	15, 16,	17,
		16,	17,	18,	19, 20,	21,
		20,	21,	22,	23, 24,	25,
		24,	25,	26,	27, 28,	29,
		28,	29,	30,	31, 32,	1	
	};
	
	private final static int[][] S = {
		{
			14,	4,	13,	1,	2,	15,	11,	8,	3,	10,	6,	12,	5,	9,	0,	7,	
			0,	15,	7,	4,	14,	2,	13,	1,	10,	6,	12,	11,	9,	5,	3,	8,
			4,	1,	14,	8,	13,	6,	2,	11,	15,	12,	9,	7,	3,	10,	5,	0,	
			15,	12,	8,	2,	4,	9,	1,	7,	5,	11,	3,	14,	10,	0,	6,	13
		},
		{
			15,	1,	8, 	14,	6, 	11,	3, 	4, 	9, 	7, 	2, 	13,	12,	0, 	5, 	10, 	
			3, 	13,	4, 	7, 	15,	2, 	8, 	14,	12,	0, 	1, 	10,	6, 	9, 	11,	5,
			0, 	14,	7, 	11,	10,	4, 	13,	1, 	5, 	8, 	12,	6, 	9, 	3, 	2, 	15, 	
			13,	8, 	10,	1, 	3, 	15,	4, 	2, 	11,	6, 	7, 	12,	0, 	5, 	14,	9
		},
		{
			10,	0, 	9, 	14,	6, 	3, 	15,	5, 	1, 	13,	12, 7, 	11,	4,	2,	8, 	
			13,	7, 	0, 	9, 	3, 	4, 	6, 	10,	2, 	8, 	5, 	14,	12,	11,	15,	1,
			13,	6, 	4, 	9, 	8, 	15,	3, 	0, 	11,	1, 	2, 	12,	5, 	10,	14,	7, 	
			1, 	10,	13,	0, 	6, 	9, 	8, 	7, 	4, 	15,	14, 3, 	11,	5,	2, 	12
		},
		{
			7, 	13,	14, 3, 	0, 	6, 	9, 	10, 1, 	2, 	8, 	5, 	11,	12,	4, 	15, 	
			13,	8, 	11,	5, 	6, 	15,	0,	3, 	4, 	7, 	2, 	12,	1, 	10,	14,	9,
			10,	6, 	9, 	0, 	12,	11, 7, 	13,	15,	1, 	3, 	14,	5, 	2, 	8, 	4, 	
			3, 	15,	0, 	6, 	10, 1, 	13,	8, 	9, 	4, 	5, 	11,	12,	7, 	2, 	14
		},
		{
			2, 	12,	4, 	1, 	7, 	10,	11,	6, 	8, 	5, 	3, 	15,	13,	0, 	14,	9, 	
			14,	11,	2, 	12,	4, 	7, 	13,	1, 	5, 	0, 	15,	10,	3, 	9, 	8, 	6,
			4, 	2, 	1, 	11,	10,	13,	7, 	8, 	15,	9, 	12,	5, 	6, 	3, 	0, 	14, 	
			11,	8, 	12,	7, 	1, 	14,	2, 	13,	6, 	15,	0, 	9, 	10, 4, 	5, 	3
		},
		{
			12,	1, 	10,	15,	9, 	2, 	6,	8, 	0, 	13,	3, 	4, 	14,	7, 	5, 	11, 	
			10,	15,	4, 	2, 	7, 	12,	9, 	5, 	6, 	1, 	13,	14,	0, 	11,	3, 	8,
			9,	14,	15,	5, 	2, 	8, 	12,	3, 	7, 	0, 	4, 	10,	1, 	13,	11,	6, 	
			4,	3, 	2, 	12,	9, 	5, 	15,	10,	11,	14,	1, 	7, 	6, 	0,	8, 	13
		},
		{
			4,	11,	2, 	14,	15,	0, 	8, 	13,	3, 	12,	9, 	7,	5, 	10,	6, 	1, 	
			13,	0, 	11,	7, 	4, 	9, 	1, 	10, 14,	3,	5, 	12,	2, 	15,	8, 	6,
			1, 	4, 	11,	13,	12,	3, 	7, 	14,	10,	15,	6, 	8, 	0, 	5, 	9, 	2, 	
			6, 	11,	13,	8, 	1, 	4, 	10,	7, 	9, 	5, 	0, 	15,	14,	2, 	3, 	12
		},
		{
			13,	2,	8, 	4, 	6, 	15,	11,	1, 	10,	9, 	3, 	14,	5, 	0, 	12,	7, 	
			1, 	15,	13,	8, 	10,	3, 	7,	4, 	12,	5, 	6, 	11,	0, 	14,	9,	2,
			7, 	11,	4, 	1, 	9, 	12,	14,	2,	0, 	6,	10,	13,	15,	3, 	5,	8, 	
			2, 	1, 	14,	7, 	4, 	10,	8,	13,	15,	12,	9, 	0, 	3, 	5, 	6,	11
		}
	};
	
	private final static int[] P = {
		16,	7, 	20,	21,	29,	12,	28,	17,
		1, 	15,	23,	26,	5, 	18,	31,	10,
		2, 	8, 	24,	14,	32,	27,	3, 	9,
		19,	13,	30,	6, 	22,	11,	4, 	25
	}; 
	
	private final static int[] CD = {
		57,	49,	41,	33,	25,	17,	9, 	1, 	58,	50,	42,	34,	26,	18,
		10,	2,	59,	51,	43,	35,	27,	19,	11,	3, 	60,	52,	44,	36, 	
		63,	55,	47,	39,	31,	23,	15,	7, 	62,	54,	46,	38,	30,	22,
		14,	6, 	61,	53,	45,	37,	29,	21,	13,	5, 	28, 20,	12,	4
	};
	
	private final static int[] leftShift = {
		1, 	1, 	2, 	2, 	2, 	2, 	2, 	2, 	1, 	2, 	2, 	2, 	2, 	2, 	2, 	1
	};
	
	private final static int[] CDtoK = {
		14,	17,	11,	24,	1, 	5, 	3, 	28,	15,	6,	21,	10,	23,	19,	12,	4,
		26,	8, 	16,	7, 	27,	20,	13,	2, 	41,	52,	31,	37,	47,	55,	30,	40,
		51,	45,	33,	48,	44,	49,	39,	56,	34,	53,	46,	42,	50,	36,	29,	32
	};
	
	protected byte[][] keys;
	
	public DESAlgorithm (byte[] key) {
		this.keys = generateKey(key);
	}
	
	private byte[] feistelFunction (byte[] R, byte[] key) {
		byte[] Er = new byte[48];
		// expand 32 to 48
		for (int i=0; i<48; i++) {
			Er[i] = R[E[i]-1];
		}
		//addition with subkey
		byte[] modRes = BinaryTools.add2(Er, key);
		//transformation S
		byte[] transRes = new byte[32];
		for (int j=0; j<8; j++) {
			byte[] B = new byte[6];
			System.arraycopy(modRes, 6*j, B, 0, 6);
			int row = BinaryTools.getInteger (B[0], B[5]),
				col = BinaryTools.getInteger (B[1], B[2], B[3], B[4]);
			System.arraycopy(BinaryTools.getByteArrayFromInt (S[row][col], 4), 0, transRes, j*4 ,4); 
		}
		// Permutation
		byte[] resultseq = new byte[32]; 
		for (int l=0; l<32; l++)
			resultseq[l] = transRes[P[l] - 1];
		return resultseq;
	}
	
	private byte[][] generateKey (byte[] key) {
		byte[][] keys = new byte[16][48];
		byte[] CD = new byte[56];	
		
		for (int j=0; j<56; j++) {
			CD[j] = key[DESAlgorithm.CD[j] - 1];
		}
		for (int i=0; i<16; i++) {	
			for (int j=0; j<48; j++) {
				keys[i][j] = CD[DESAlgorithm.CDtoK[j] - 1];
			}
			CD = circleLeftShift (CD, DESAlgorithm.leftShift[i]);
		}
		return keys;
	}

	private byte[] circleLeftShift(byte[] cD, int i) {
		byte[]	C = new byte[28],
				D = new byte[28];
		System.arraycopy(cD, 0, C, 0, 28);
		System.arraycopy(cD, 28, D, 0, 28);
		byte[] result = new byte[56];
		System.arraycopy(result, 0, BinaryTools.circleLeftShift(C, DESAlgorithm.leftShift[i]), 0, 28);
		System.arraycopy(result, 28, BinaryTools.circleLeftShift(D, DESAlgorithm.leftShift[i]), 0, 28);
		return result;
	}
	
	private byte[] doCypherRounds(byte[] buffer, boolean isReverse) {
		byte[] permutatedBlock = new byte[64];
		// do permutation
		for (int j=0; j < 64; j++) {
			permutatedBlock [j] = buffer[ip[j]-1];
		}
		// do forward feistel func
		byte[]	L = new byte[32],
				R = new byte[32];
		System.arraycopy(permutatedBlock, 0, L, 0, 32);
		System.arraycopy(permutatedBlock, 32, R, 0, 32);
		if (isReverse) {
			for (int ccicle = 15; ccicle >= 0; ccicle--) {
				byte[] Li = L;
				L = BinaryTools.add2(R, feistelFunction (L, this.keys[ccicle]));
				R = Li;
			}
		} else {
			for (int ccicle = 0; ccicle < 16; ccicle++) {
				byte[] Ri1 = R;
				R = BinaryTools.add2(L, feistelFunction (R, this.keys[ccicle]));
				L = Ri1;
			}
		}
		// do end permutation
		byte[] T = new byte[64];
		System.arraycopy(L,0,T,0,32);
		System.arraycopy(R,0,T,32,32);
		byte[] result = new byte[64];
		for (int i=0; i<64; i++) {
			result[i] = T[DESAlgorithm.inverseIP[i]-1];
		}
		return result;
	}
	
	private byte[] doEncryption(byte[] words, boolean isReverse) {
		byte[]	buffer = new byte[64];
		ArrayList<byte[]> resultedWords = new ArrayList<byte[]>();
		int offset = 0;
		for (byte b : words) {
			if (offset == 64) {
				resultedWords.add(doCypherRounds(buffer, isReverse));
			}
			offset %= 64;
			buffer [offset++] = b;
		}
		if (offset > 0) {
			resultedWords.add(doCypherRounds(buffer, isReverse));
		}
		byte[] result = new byte[resultedWords.size()*64];
		int copyOffset = 0;
		for (byte[] cw : resultedWords) {
			System.arraycopy(cw, 0, result, copyOffset*64, 64);
			copyOffset++;
		}
		return result;
	}
	
	@Override
	public byte[] encode (byte[] words) {
		return doEncryption(words, false);
	}

	@Override
	public byte[] decode(byte[] cipherWords) {
		return doEncryption(cipherWords, true);
	}
}
